1. Retour sur la machine à état UML
Soit la machine à état suivante :
1.1. Implémentation intuitive
public void insererPiece() {
if (etat == A_PIECE) {
System.out.println("Vous ne pouvez plus insérer de pièces");
} else if (etat == EPUISE) {
System.out.println("Vous ne pouvez pas insérer de pièce, nous sommes en rupture de stock");
} else if (etat == VENDU) {
System.out.println("Veuillez patienter, le bonbon va tomber");
} else if (etat == SANS_PIECE) {
etat = A_PIECE;
System.out.println("Vous avez inséré une pièce");
}
}
1.2. Erreur d’implémentations
-
Ce code n’adhère pas au principe Ouvert-Fermé.
-
Cette conception n’est pas orientée objet.
-
Les transitions ne sont pas explicites. Elles sont enfouies au milieu d’un tas d’instructions conditionnelles.
-
Nous n’avons pas encapsulé ce qui varie.
-
Les ajouts ultérieurs sont susceptibles de provoquer des bugs dans le code.
1.3. Une meilleure implémentation
-
Définir une nouvelle interface
Etatqui contiendra une méthode pour chaque action -
Implémenter une classe pour chaque
Etat. Elles seront responsable du comportement. -
Se débarrasser de toutes les instructions conditionnelles et les remplacer par une délégation à la classe adéquate.
1.4. Illutration
Etape 1 :
Etape 2
public class EtatSansPiece implements Etat {
Distributeur distributeur;
public EtatSansPiece(Distributeur distributeur) {
this.distributeur = distributeur;
}
public void insererPiece() {
System.out.println("Vous avez inséré une pièce");
distributeur.setEtat(distributeur.getEtatAPiece());
}
...
}
Etape 3
public class Distributeur {
Etat etatEpuise;
Etat etatSansPiece;
Etat etatAPiece;
Etat etatVendu;
Etat etat = etatSansPiece;
...
public void insererPiece() {
etat.insererPiece();
}
...
}
1.5. Le patron Etat
Etat permet à un objet de modifier son comportement, quand son état interne change. Tout se passe comme si l’objet changeait de classe.
2. Construire ses applications
Pour générer un programme, une documentation, à partir des sources, on peut :
-
Soit utiliser un environnement intégré comme eclipse
-
Soit construire les sorties (on parle de Build) à partir des sources
Nous nous intéressons dans cette section à cette deuxième catégorie.
Il existe plusieurs outils :
2.1. Les scripts
#!/bin/sh
UML='model.uml'
TYPE='PNG'
DOCLETPATH='/Users/bruel/dev/teaching/dut/cpoa/doclet'
PUMLPATH='/Users/bruel/dev/teaching/dut/cpoa/util'
echo "Creating $UML..."
javadoc \
-private \
-quiet \
-J-DdestinationFile=$UML \
-J-DcreatePackages=false \
-J-DshowPublicMethods=true \
-J-DshowPublicConstructors=false \
-J-DshowPublicFields=true \
-doclet de.mallox.doclet.PlantUMLDoclet -docletpath $DOCLETPATH/plantUmlDoclet.jar \
-sourcepath src src/**
echo "Done."
TYPE='png'
echo "Converting $UML to $TYPE..."
java -jar $PUMLPATH/plantuml.jar \
-config $PUMLPATH/config.cfg \
-t $TYPE $UML
echo "Done."
set UML=TD1.uml
set TYPE='PNG'
set DOCLETPATH=E:\IUT-S3\CPOA\TP1\SuperCanardBof
echo "Creating %UML%..."
rem javadoc -private -quiet -J-DdestinationFile=%UML% -J-DcreatePackages=false -J-DshowPublicMethods=true -J-DshowPublicConstructors=false -J-DshowPublicFields=true -doclet de.mallox.doclet.PlantUMLDoclet -docletpath %DOCLETPATH%\plantUmlDoclet.jar src\canard\*.java
echo "Done."
javadoc -J-DdestinationFile=%UML% -J-DcreatePackages=false -J-DshowPublicMethods=true -J-DshowPublicConstructors=false -J-DshowPublicFields=true -doclet de.mallox.doclet.PlantUMLDoclet -docletpath plantUmlDoclet.jar src\appli\*.java src\armes\*.java src\armes\impl\*.java
set TYPE='png'
echo "Converting %UML% to $TYPE..."
java -jar %DOCLETPATH%\plantuml.jar -config "%DOCLETPATH%\config.cfg" -t %TYPE% %UML%
echo "Done."
- Avantages
-
-
Faciles
-
Rapides
-
Beaucoup d’exemples
-
- Inconvénients
-
-
Pas portables sur d’autres systèmes (no comment ;-)
-
Peu lisibles
-
Peu évolutifs
-
2.2. make
1 #-----------------------------------------------------
2 ICONSDIR=images/icons
3 IMAGESDIR=images
4 STYLE=/Users/bruel/Dropbox/Public/dev/asciidoc/stylesheets/golo-jmb.css
5 DOCTOR=asciidoctor -a icons -a iconsdir=$(ICONSDIR) -a images=$(IMAGESDIR) -a source-highlighter=$(HIGHLIGHT)
6 DECK=swiss
7 EXT=asc
8 PANDOC=pandoc
9 OUTPUT=.
10 DEP=definitions.txt glossaire.txt refs.txt
11 #-----------------------------------------------------
12
13 all: $(OUTPUT)/*.html
14
15 images/%.png: images/%.plantuml
16 @echo '==> Compiling plantUML files to generate PNG'
17 java -jar plantuml.jar $<
18
19 %.html: %.$(EXT) $(DEP)
20 @echo '==> Compiling asciidoc files with Asciidoctor to generate HTML'
21 $(DOCTOR) -a toc2 -b html5 -a numbered -a eleve $<
22
23 %.deckjs.html: %.$(EXT) $(DEP)
24 @echo '==> Compiling asciidoc files to generate Deckjs'
25 $(DOCTOR) -T /Users/bruel/dev/asciidoctor-backends/haml/deckjs/ -a slides \
26 -a data-uri -a deckjs_theme=$(DECK) \
27 -a icons -a iconsdir=$(ICONSDIR) \
28 -a images=$(IMAGESDIR) -a prof -o $@ $<
29
30 %-sujet.html: %.$(EXT) $(DEP)
31 @echo '==> Compiling asciidoc files with Asciidoctor to generate HTML'
32 $(DOCTOR) -a compact -a theme=compact -b html5 -a numbered -a eleve \
33 -a data-uri $< -o $@
34
35 %-prof.html: %.$(EXT) $(DEP)
36 @echo '==> Compiling asciidoc files with Asciidoctor to generate HTML'
37 $(DOCTOR) -a prof -a correction -a theme=compact -b html5 -a numbered \
38 -a data-uri $< -o $@
%.html: %.$(EXT) $(DEP)
@echo '==> Compiling asciidoc files with Asciidoctor to generate HTML'
$(DOCTOR) -a toc2 -b html5 -a numbered -a eleve $<
Exemple d’utilisation :
$ make wip.html ==> Compiling asciidoc files with Asciidoctor to generate HTML asciidoctor -a icons -a iconsdir=images/icons -a images=images -a source-highlighter=pygments -a toc2 -b html5 -a numbered -a eleve wip.asc ... $ make wip.html make: 'wip.html' is up to date.
2.3. ant
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project default="main" name="EssaiBuild">
<target name="main">
<echo message="Version d'Ant utilisée: ${ant.version}"/>
<javadoc doclet="de.mallox.doclet.PlantUMLDoclet"
docletpath="plantUmlDoclet.jar"
access="private"
additionalparam=
"-encoding utf-8 -J-DdestinationFile=uml.txt -J-DcreatePackages=false -J-DshowPublicMethods=true -J-DshowPublicConstructors=false -J-DshowPublicFields=true"
>
<packageset dir="../src">
<include name="**"/>
</packageset>
</javadoc>
<java jar="plantuml.jar" fork="true" maxmemory="128m">
<arg value="uml.txt"/>
</java>
</target>
</project>
Exemple d’utilisation :
$ ant main Buildfile: build.xml main: [javadoc] Generating Javadoc [javadoc] Javadoc execution [javadoc] Loading source files for package pizzeriafactorysample... [javadoc] Constructing Javadoc information... [javadoc] PlantUMLDoclet.createPlantUml() - start [javadoc] open outputfile: uml.txt [javadoc] write interfaces/ abstract classes... [javadoc] write content... ... [javadoc] skip association for Pizza --> java.lang.String [javadoc] skip association for Pizza --> java.lang.String [javadoc] skip association for Pizza --> java.lang.String [javadoc] skip association for Pizza --> java.util.ArrayList [javadoc] PlantUMLDoclet.createPlantUml() - end BUILD SUCCESSFUL Total time: 9 seconds
Exemple d’tilisation dans eclipse pour la génération de fichier
du type TD.uml :
-
Créer un répertoire tools et mettre dedans :
-
Plantuml.jar -
Plantumldoclet.jar -
build.xml
-
-
Faire un Click_Droit sur
build.xmlet chosirBien choisir celui avec les points de suspensions.
-
Dans l’onglet
Environment:-
Créer une nouvelle variable de nom
Pathet de valeur : le répertoire de la JDK (où se trouvejavadocoujavadoc.exe)
-
-
Exécuter Run
Faire Refresh dans le navigateur pour voir les 2 fichiers générés ( uml.txtetuml.png). -
Il suffit de faire un Click_Droit sur
build.xmlet chosir (SANS les 3 points de suspensions) pour relancer la génération du diagramme.
2.4. Maven
Convention over Configuration
Exemples de conventions :
-
le code source est supposé se trouver dans
${basedir}/src/main/java -
les différentes ressources dans
${basedir}/src/main/resources -
les tests dans
${basedir}/src/test -
un projet est supposé produire un fichier
JAR -
Maven suppose que vous voulez compiler en bytecode dans
${basedir}/target/classes -
et ensuite créer votre fichier
JARdistribuable dans${basedir}/target
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>org.sonatype.mavenbook</groupId>
<artifactId>my-project</artifactId>
<version>1.0</version>
</project>
La commande :
$ mvn install
-
va traiter les ressources,
-
compiler le source,
-
exécuter les tests unitaires,
-
créer un JAR, et
-
installer ce JAR dans le dépôt local.
La commande :
$ mvn site
va créer un fichier index.html dans target/site contenant des liens vers
la JavaDoc et quelques rapports sur votre code source.
|
|
Pour comparer, voici l’équivalent ant :
|
2.5. Ivy
<ivy-module version="2.0">
<info organisation="org.apache" module="hello-ivy"/>
<dependencies>
<dependency org="commons-lang" name="commons-lang" rev="2.0"/>
<dependency org="commons-cli" name="commons-cli" rev="1.0"/>
</dependencies>
</ivy-module>
<project xmlns:ivy="antlib:org.apache.ivy.ant" name="hello-ivy" default="run">
...
<!-- =================================
target: resolve
================================= -->
<target name="resolve" description="--> retrieve dependencies with ivy">
<ivy:retrieve />
</target>
</project>
2.6. Graddle
Gradle combine la flexibilité de ant avec les conventions de Maven mais
évite les inconvénients de XML.
task hello {
doLast {
println 'Hello world!'
}
}
$ gradle hello :hello Hello world! BUILD SUCCESSFUL Total time: 3.486 secs
3. Le patron proxy
3.1. Le problème
On a besoin de références à un objet, qui soient plus créatives et plus sophistiquées qu’un simple pointeur.
3.2. Le patron Proxy
Procuration (Proxy) fournit à un tiers un mandataire ou un remplaçant, pour contrôler l’accès à cet objet.
3.3. Utilisations
-
Une procuration à distance fournit un représentant local d’un objet situé dans un espace adresse différent.
-
Une procuration virtuelle crée des objets lourds à la demande.
-
Une procuration de protection contrôle l’accès à l’objet original. Les procurations de protection sont utiles quand les objets doivent satisfaire différents droits d’accès.
-
Une référence intelligente est le remplaçant d’un pointeur brut, qui réalise des opérations supplémentaires, lors de l’accès à l’objet. Quelques utilisations typiques sont :
-
décompte du nombre des références faites à un objet réel, de sorte que celui-ci puisse être libéré automatiquement, dés qu’il n’y a plus de références ;
-
charger en mémoire un objet persistant quand il est référencé pour la première fois ;
-
vérifier, avant d’y accéder, que l’objet réel est verrouillé, pour être sûr qu’aucun autre objet ne pourra le changer.
-
3.4. Exemple concret : RMI
Remote Method Invocation
import java.rmi.*;
public interface MonService extends Remote {
public String direBonjour() throws RemoteException;
}
import java.rmi.*;
import java.rmi.server.*;
public class MonServiceImpl extends UnicastRemoteObject implements MonService {
public String direBonjour() {
return "Le serveur dit 'Bonjour'";
}
public MonServiceImpl() throws RemoteException {}
public static void main (String[] args) {
try {
MonService service = new MonServiceImpl();
Naming.rebind("BonjourDistant", service);
} catch(Exception ex) {
ex.printStackTrace();
}
}
}
MonService service =
(MonService) Naming.lookup("rmi://127.0.0.1/BonjourDistant");
...
service.direBonjour();
4. Le patron Itérateur
4.1. Le problème
On veut pouvoir :
-
pour accéder au contenu d’un objet d’un agrégat sans en révéler la représentation interne ;
-
pour gérer simultanément plusieurs parcours dans des agrégats d’objets ;
-
pour offrir une interface uniforme pour les parcours au travers de diverses structures agrégats (c’est-à-dire, pour permettre l’itération polymorphe).
4.2. Le patron Itérateur
Itérateur (Iterator) fournit un moyen d’accès séquentiel aux éléments d’un agrégat d’objets, sans mettre à découvert la représentation interne de celui-ci.
4.3. Exemple concret
# Saluer tout le monde
def say_hi
if @names.nil?
puts "..."
elsif @names.respond_to?("each")
# @names est une liste de noms : traitons-les uns par uns
@names.each do |name|
puts "Hello #{name}!"
end
else
puts "Hello #{@names}!"
end
end
5. Le patron Composite
5.1. Le problème
On veut pouvoir :
-
représenter des hiérarchies de l’individu.
-
que le client n’ait pas à se préoccuper de la différence entre "combinaisons d’objets" et "objets individuels". Les clients pourront traiter de façon uniforme tous les objets de la structure composite.
5.2. Le patron Composite
Composite permet de composer des objets en des structures arborescentes pour représenter des hiérarchies composant/composé. Permet au client de traiter d’une façon unique les objets et les combinaisons d’objets.
5.3. Exemple concret
import java.util.ArrayList;
interface Graphic {
public void print();
}
class CompositeGraphic implements Graphic {
private ArrayList<Graphic> mChildGraphics = new ArrayList<Graphic>();
public void print() {
for (Graphic graphic : mChildGraphics) {
graphic.print();
}
}
public void add(Graphic graphic) {
mChildGraphics.add(graphic);
}
public void remove(Graphic graphic) {
mChildGraphics.remove(graphic);
}
}
5.4. un "Anti" exemple
Que pensez-vous de cette définition de Composite ?
|
|
On appelle ces modèles des "Patrons abîmés" (anti-patterns). |
6. Retour sur le refactoring Banque
6.1. Le problème
Remplacer tous ces switch cases
continuer = true;
while (continuer) {
AAB.afficherMenu(monAg);
choix = lect.next();
choix = choix.toLowerCase();
switch (choix) {
...
case "p" :
System.out.print("Propriétaire -> ");
nom = lect.next();
AAB.comptesDUnPropretaire (monAg, nom);
break;
...
-
Afficher une liste séparemment du switch
//AAB.afficherMenu(monAg); System.out.println("Menu de " + ag.getNomAgence() + " (" + ag.getLocAgence() + ")"); System.out.println("l - Liste des comptes de l'agence"); ... System.out.println("p - voir les comptes d'un Propriétaire (par son nom)"); ... System.out.print("Choix -> "); } -
Tester tous les choix pour actionner la bonne option
... case "p" : System.out.print("Propriétaire -> "); nom = lect.next(); AAB.comptesDUnPropretaire (monAg, nom); break; ...
6.2. Une solution
-
Des listes
-
Des options de menu qui encapsulent l’action à réaliser
public interface ActionList extends Action {
public String listTitle();
public int size();
public boolean addAction(Action ac);
public boolean removeAction(Action ac);
public String[] listOfActions() ;
}
public interface Action {
public String actionMessage ();
public void execute(AgenceBancaire ag);
}
public class Action1 implements Action {
private String lineMessage;
...
public String actionMessage() {
return this.lineMessage;
}
public void execute(AgenceBancaire ab) {
...
ab.afficher();
}
}
action.execute(ab);
Action a1 = new Action1("Liste des comptes de l'agence");
Action a2 = new Action2("Voir un compte (par son numéro)");
Action a3 = new Action3(...);
ActionList al1 = new ActionListAgenceBancaire("Menu Général");
al1.addAction(a1);
al1.addAction(a2);
public void execute(AgenceBancaire ab) throws Exception {
...
while (true) {
this.printMenu();
choice = this.readResponse();
...
this.myMenu.get(choice).execute(ab);
...
public interface ActionList extends Action {
}